/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.refer;

import java.io.IOException;

import org.zhiqim.git.GitConstants;
import org.zhiqim.git.GitFileLock;
import org.zhiqim.git.GitRefer;
import org.zhiqim.git.GitWalker;
import org.zhiqim.git.objer.model.PersonIdent;
import org.zhiqim.git.objer.objectid.ObjectId;
import org.zhiqim.git.objer.objectid.RevCommit;
import org.zhiqim.git.objer.objectid.RevObject;
import org.zhiqim.git.util.GitValidates;

import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;

/**
 * 引用更新器，用于引用创建、修改和删除的管理
 *
 * @version v1.0.0 @author zouzhigang 2017-4-12 新建与整理
 */
public class RefUpdater implements GitConstants
{
    private final GitRefer refer;
    private final Ref ref;
    
    private RefUpdateResult result = RefUpdateResult.NOT_ATTEMPTED;
    private GitFileLock lock;                   //文件锁
    
    private boolean force;                     //是否强制更新
    private ObjectId oldId;                     //原对象编号，如果有，则锁定该文件
    private ObjectId newId;                     //新对象编号
    private ObjectId expId;                     //期望对象编号
    
    private PersonIdent refLogIdent;            //引用日志提交者
    private String refLogMessage;               //引用日志信息
    private boolean refLogIncludeResult;       //引用日志是否包含操作结果

    /************************************************************************************/
    //构造函数&基础参数获取
    /************************************************************************************/
    
    public RefUpdater(GitRefer refer, Ref ref)
    {
        this.refer = refer;
        this.ref = ref;
        
        this.oldId = ref.getObjectId();
        this.refLogMessage = "";
    }

    public GitRefer getRefer()
    {
        return refer;
    }
    
    public Ref getRef()
    {
        return ref;
    }
    
    public String getName()
    {
        return ref.getName();
    }

    /************************************************************************************/
    //参数获取&设置
    /************************************************************************************/

    public RefUpdateResult getResult()
    {
        return result;
    }
    
    public ObjectId getOldObjectId()
    {
        return oldId;
    }
    
    public ObjectId getNewObjectId()
    {
        return newId;
    }
    
    public PersonIdent getRefLogIdent()
    {
        return refLogIdent;
    }
    
    public String getRefLogMessage()
    {
        return refLogMessage;
    }

    public RefUpdater setForceUpdate(boolean force)
    {
        this.force = force;
        return this;
    }

    protected RefUpdater setOldObjectId(ObjectId old)
    {
        this.oldId = old;
        return this;
    }
    
    public RefUpdater setNewObjectId(ObjectId oid)
    {
        this.newId = oid;
        return this;
    }

    public RefUpdater setExpectedOldObjectId(ObjectId oid)
    {
        this.expId = oid;
        return this;
    }

    public RefUpdater setRefLogIdent(PersonIdent pi)
    {
        this.refLogIdent = pi;
        return this;
    }

    /**
     * 设置引用日志信息
     * 
     * @param message       日志消息内容
     * @param appendResult  增加操作结果（快进和强制更新设置为true）
     */
    public RefUpdater setRefLogMessage(String message, boolean appendResult)
    {
        if (message == null && !appendResult)
            return disableRefLog();
        
        this.refLogIncludeResult = appendResult;
        this.refLogMessage = (message == null)?"":message;
        return this;
    }

    /** 不记录更新日志 */
    public RefUpdater disableRefLog()
    {
        refLogMessage = null;
        refLogIncludeResult = false;
        return this;
    }

    /************************************************************************************/
    //加锁&取消锁
    /************************************************************************************/

    protected boolean tryLock(boolean deref) throws IOException
    {
        Ref ref = getRef();
        if (deref)
            ref = ref.getUltimateTarget();
        
        String name = ref.getName();
        lock = new GitFileLock(refer.fileFor(name));
        if (!lock.lock())
            return false;
        
        ref = refer.findRef(name);
        setOldObjectId(ref != null ? ref.getObjectId() : null);
        return true;
    }

    public void unlock()
    {
        if (lock != null)
        {
            lock.unlock();
            lock = null;
        }
    }
    
    /************************************************************************************/
    //快捷方式关联
    /************************************************************************************/

    /**
     * 关联快捷方式到目标引用上
     * 
     * @param target        目标引用
     * @return              关联结构
     * @throws IOException  IO异常
     */
    public RefUpdateResult link(String target) throws IOException
    {
        Asserts.asserts(target.startsWith(P_REFS_), "目标引用全称必须以ref/开头");
        
        if (refer.isNameConflicting(getName()))
        {//判断引用名称是否冲突
            return RefUpdateResult.LOCK_FAILURE;
        }
        
        try
        {
            if (!tryLock(false))
            {//请求锁失败
                return RefUpdateResult.LOCK_FAILURE;
            }
            
            Ref oldRef = refer.findRef(getName());
            if (oldRef != null && oldRef.isSymbolic())
            {//原引用存在并且是快捷方式
                final Ref dst = oldRef.getTarget();
                if (target.equals(dst.getName()))
                {//未修改
                    return result = RefUpdateResult.NO_CHANGE;
                }
            }

            if (oldRef != null && oldRef.getObjectId() != null)
            {//设置原对象编号
                setOldObjectId(oldRef.getObjectId());
            }
            
            Ref newRef = refer.findRef(target);
            if (newRef != null && newRef.getObjectId() != null)
            {//设置新对象编号
                setNewObjectId(newRef.getObjectId());
            }
            
            return result = doLink(target);
        }
        catch (IOException x)
        {
            result = RefUpdateResult.IO_FAILURE;
            throw x;
        }
        finally
        {
            unlock();
        }
    }
    
    /**
     * 创建快捷方式引用，并指到目标引用（ref: refs/heads/master\n）
     * 
     * @param target        目标引用
     * @return              更新结果
     * @throws IOException  文件异常
     */
    private RefUpdateResult doLink(final String target) throws IOException
    {
        lock.setNeedStatInformation(true);
        lock.write(Bytes.BU.toBytes(GitRefer.P_SYMREF + target + _LF_));

        if (refLogMessage != null)
            refer.log(this, refLogMessage, false);
        
        if (!lock.commit())
            return RefUpdateResult.LOCK_FAILURE;

        return force?RefUpdateResult.FORCED:RefUpdateResult.NEW;
    }
    
    /************************************************************************************/
    //更新
    /************************************************************************************/

    /**
     * 强制更新到新的对象上
     * 
     * @return              更新结果
     * @throws IOException  处理异常
     */
    public RefUpdateResult forceUpdate() throws IOException
    {
        force = true;
        return update();
    }
    
    /**
     * 温和更新到新的对象上（更新前检查合并）
     * 
     * @return              更新结果
     * @throws IOException  处理异常
     */
    public RefUpdateResult update() throws IOException
    {
        return update(new GitWalker(refer.git()));
    }

    /**
     * 温和更新到新的对象上（更新前检查合并），并指定RevWalk
     * 
     * @param walk          对象步行器
     * @return              更新结果
     * @throws IOException  处理异常
     */
    public RefUpdateResult update(GitWalker walk) throws IOException
    {
        Asserts.assertNotNull(newId, "更新引用时，新对象编号不能为null");
        
        try
        {
            return result = doUpdateOrDelete(walk, true);
        }
        catch (IOException x)
        {
            result = RefUpdateResult.IO_FAILURE;
            throw x;
        }
    }
    
    /************************************************************************************/
    //删除
    /************************************************************************************/

    /**
     * 删除指定的引用
     * 
     * @return              删除状态
     * @throws IOException  异常
     */
    public RefUpdateResult delete() throws IOException
    {
        return delete(new GitWalker(refer.git()));
    }

    /**
     * 删除指定的引用，指定对象步行器
     * 
     * @param walker        对象步行器
     * @return              删除状态
     * @throws IOException  异常
     */
    public RefUpdateResult delete(GitWalker walker) throws IOException
    {
        if (GitValidates.isCurrentBranche(refer, ref))
        {//当前分枝不能删除
            return result = RefUpdateResult.REJECTED_CURRENT_BRANCH;
        }
        
        try
        {
            return result = doUpdateOrDelete(walker, false);
        }
        catch (IOException x)
        {
            result = RefUpdateResult.IO_FAILURE;
            throw x;
        }
    }
    
    /************************************************************************************/
    //更新&删除实现
    /************************************************************************************/
    
    private RefUpdateResult doUpdateOrDelete(GitWalker walker, boolean update) throws IOException
    {
        if (refer.isNameConflicting(getName()))
            return RefUpdateResult.LOCK_FAILURE;
        
        try
        {
            if (!tryLock(true))
                return RefUpdateResult.LOCK_FAILURE;
            
            if (expId != null)
            {
                ObjectId o = (oldId != null)?oldId:ZEROID;
                if (!ObjectId.equals(expId, o))
                    return RefUpdateResult.LOCK_FAILURE;
            }
            
            if (oldId == null)
            {//原对象编号不存在，新增，删除直接返回
                return doUpdateOrDelete(RefUpdateResult.NEW, update);
            }

            RevObject newObj = walker.parseNullable(newId);
            RevObject oldObj = walker.parseNullable(oldId);
            if (newObj == oldObj)
            {//未修改，更新直接返回，删除操作
                doUpdateOrDelete(RefUpdateResult.NO_CHANGE, update);
            }

            if (newObj instanceof RevCommit && oldObj instanceof RevCommit)
            {//两个提交信息进行合并
                if (walker.isFastward((RevCommit) oldObj, (RevCommit) newObj))
                {
                    return doUpdateOrDelete(RefUpdateResult.FAST_FORWARD, update);
                }
            }

            if (force)
                return doUpdateOrDelete(RefUpdateResult.FORCED, update);
            
            return RefUpdateResult.REJECTED;
        }
        finally
        {
            unlock();
        }
    }
    
    /** 已知状态，更新&删除处理 */
    private RefUpdateResult doUpdateOrDelete(RefUpdateResult status, boolean update) throws IOException
    {
        if (update)
            return doUpdate(status);
        else
            return doDelete(status);
    }
    
    /** 更新到文件 */
    private RefUpdateResult doUpdate(RefUpdateResult result) throws IOException
    {
        if (result == RefUpdateResult.NO_CHANGE)
            return result;
        
        lock.setNeedStatInformation(true);
        lock.write(newId);

        if (refLogMessage != null)
        {//有日志消息写入
            String message = refLogMessage;
            if (refLogIncludeResult)
            {
                String strResult = RefUpdateResult.toResultString(result);
                if (strResult != null)
                    message = (message.length() > 0)?(message + ": " + strResult):strResult;
            }
            refer.log(this, message, true);
        }
        
        if (!lock.commit())
            return RefUpdateResult.LOCK_FAILURE;

        return result;
    }
    
    /** 删除到文件 */
    private RefUpdateResult doDelete(RefUpdateResult status) throws IOException
    {
        if (getRef().getUltimateTarget().isStored())
            refer.delete(this);
        
        return status;
    }
}
